package cn.lingyangwl.agile.system.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.lingyangwl.agile.auth.AuthApi;
import cn.lingyangwl.agile.auth.model.entity.LocalUser;
import cn.lingyangwl.agile.common.security.utils.SecurityUtils;
import cn.lingyangwl.agile.model.BasePlusPO;
import cn.lingyangwl.agile.model.constants.CommonCons;
import cn.lingyangwl.agile.model.constants.TenantCons;
import cn.lingyangwl.agile.model.enums.BoolEnum;
import cn.lingyangwl.agile.model.enums.ConfigKeyEnum;
import cn.lingyangwl.agile.model.enums.RoleEnum;
import cn.lingyangwl.agile.model.module.auth.AuthUserTenant;
import cn.lingyangwl.agile.model.module.auth.LoginUser;
import cn.lingyangwl.agile.model.module.config.param.WebSiteParam;
import cn.lingyangwl.agile.model.module.tenant.TenantContext;
import cn.lingyangwl.agile.system.assembly.SysTenantAssembly;
import cn.lingyangwl.agile.system.config.TenantProperties;
import cn.lingyangwl.agile.system.constants.CacheCons;
import cn.lingyangwl.agile.system.constants.SysErrorEnum;
import cn.lingyangwl.agile.system.manager.*;
import cn.lingyangwl.agile.system.mapper.SysTenantMapper;
import cn.lingyangwl.agile.system.model.entity.SysRole;
import cn.lingyangwl.agile.system.model.entity.SysTenant;
import cn.lingyangwl.agile.system.model.entity.SysTenantPackage;
import cn.lingyangwl.agile.system.model.entity.SysTenantUser;
import cn.lingyangwl.agile.system.model.rqrs.config.ConfigSaveReq;
import cn.lingyangwl.agile.system.model.rqrs.role.RoleSaveReq;
import cn.lingyangwl.agile.system.model.rqrs.tenant.*;
import cn.lingyangwl.agile.system.model.rqrs.user.UserResp;
import cn.lingyangwl.agile.system.service.*;
import cn.lingyangwl.agile.tenant.core.utils.TenantUtils;
import cn.lingyangwl.framework.cache.core.service.CacheService;
import cn.lingyangwl.framework.core.utils.spring.SpringUtils;
import cn.lingyangwl.framework.mybatis.plus.utils.PageUtil;
import cn.lingyangwl.framework.tool.core.CollectionUtils;
import cn.lingyangwl.framework.tool.core.DateUtils;
import cn.lingyangwl.framework.tool.core.StringUtils;
import cn.lingyangwl.framework.tool.core.exception.Assert;
import cn.lingyangwl.framework.tool.core.exception.BizException;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yitter.idgen.YitIdHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static cn.lingyangwl.agile.system.constants.SysErrorEnum.TENANT_CAN_NOT_UPDATE_SYSTEM;
import static cn.lingyangwl.agile.system.constants.SysErrorEnum.TENANT_NOT_EXISTS;

/**
 * @author shenguangyang
 */
@Slf4j
@Service
public class SysTenantServiceImpl extends ServiceImpl<SysTenantMapper, SysTenant> implements SysTenantService {
    @Resource
    private SysTenantAssembly tenantAssembly;
    @Resource
    private AuthApi authApi;
    @Resource
    private SysTenantPackageManager tenantPackageManager;
    @Resource
    private SysTenantManager tenantManager;
    @Resource
    private SysTenantUserManager tenantUserManager;
    @Resource
    private AuthorizeService authorizeService;
    @Resource
    private SysRoleService roleService;
    @Resource
    private SysUserService userService;
    @Resource
    private SysRoleManager roleManager;
    @Resource
    private TenantProperties tenantProperties;
    @Resource
    private CacheService cacheService;
    @Resource
    private SysConfigService configService;
    @Resource
    @Lazy
    private SysMqManager mqManager;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public SysTenant validTenant(String id) {
        SysTenant tenant = this.getById(id);
        if (tenant == null) {
            log.error("tenantId: {} not exit", id);
            throw new BizException(TENANT_NOT_EXISTS);
        }
        if (tenant.getStatus().equals(CommonCons.UseStats.DISABLE)) {
            throw new BizException(SysErrorEnum.TENANT_DISABLE);
        }
        if (Objects.nonNull(tenant.getBeginTime()) && tenant.getBeginTime().after(new Date())) {
            throw new BizException(SysErrorEnum.TENANT_NOT_EFFECTIVE_TIME);
        }

        if (Objects.nonNull(tenant.getEndTime()) && DateUtils.isExpired(tenant.getEndTime())) {
            throw new BizException(SysErrorEnum.TENANT_EXPIRE);
        }
        return tenant;
    }

    @Override
    public List<SysTenant> listEffectiveTenant(Collection<String> idList) {
        LambdaQueryWrapper<SysTenant> queryWrapper = SysTenant.lqw()
                .in(CollectionUtils.isNotEmpty(idList), SysTenant::getId , idList)
                .eq(SysTenant::getStatus, Integer.valueOf(TenantCons.Status.NORMAL));
        return super.list(queryWrapper);
    }

    @Override
    public TenantResp getEffectiveTenant(String id) {
        LambdaQueryWrapper<SysTenant> queryWrapper = SysTenant.lqw()
                .eq(SysTenant::getId , id)
                .eq(SysTenant::getStatus, Integer.valueOf(TenantCons.Status.NORMAL));
        return tenantAssembly.toTenantResp(super.getOne(queryWrapper));
    }

    @Override
    public long countUserLinkTenant(Long id) {
        // 查找出已被关联的用户数量
//        WorkspaceContentHolder.get().setTenantId(id);
//        return sysUserAppRelManager.count(SysUserAppRel.lqw().eq(SysUserAppRel::get, id));
        return 0L;
    }

    @Override
    public List<SysTenant> pageTenant(Integer pageNum, Integer pageSize) {
        return this.page(new Page<>(pageNum, pageSize)).getRecords();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteTenant(String id) {
        SysTenant tenant = validateUpdateTenant(id);
        if (!super.removeById(id)) {
           throw new BizException("删除租户失败");
        }
        List<String> ids = new ArrayList<>();
        if (tenant.getIsMainTenant()) {
            List<SysTenant> subTenantList = tenantManager.listSubTenant(tenant.getId());
            ids.addAll(subTenantList.stream().map(SysTenant::getId).collect(Collectors.toList()));
        }

        for (String deleteTenantId : ids) {
            TenantUtils.execute(deleteTenantId, () -> {
                // 删除租户和用户关系
                tenantUserManager.deleteAllUser();

                // 删除网站配置
                configService.deleteConfigByKey(ConfigKeyEnum.WEB_SITE.getKey());

                // 删除租户下的所有用户角色
                authorizeService.deleteTenantAllRole();

                // 删除租户下的所有部门
                authorizeService.deleteTenantAllDept();

                // 删除缓存
                String cacheKey = CacheCons.TENANT_KEY_DOMAIN + tenant.getDomain();
                cacheService.opsForKey().delete(cacheKey);
            });
        }
        // 发送刷新消息. 注意，需要事务提交后，在进行发送刷新消息。如果数据库出现异常提交失败, 这时做刷新缓存或者通知处理, 很有可能有问题
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                mqManager.sendDeleteTenantMsg(tenant);
            }
        });

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchDeleteTenant(List<String> idList) {
        SysTenantServiceImpl aopProxy = SpringUtils.getAopProxy(this);
        idList.forEach(aopProxy::deleteTenant);
    }

    @Override
    public IPage<TenantResp> pageTenant(TenantPageReq query) {
        AuthUserTenant tenant = SecurityUtils.getLoginUser().getTenant();

        // 不是平台租户且非主租户则不展示租户列表
        if (!tenant.getIsMgtScene() && !tenant.mainTenant()) {
            return new Page<>();
        }

        Long sceneId = null;
        // 场景管理租户且非平台场景管理租户, 则查询指定场景的租户
        if (tenant.getIsMgtScene() && !tenant.getIsSysAdmin()) {
            SysTenant currentTenant = this.tenantManager.getById(tenant.getTenantId());
            sceneId = currentTenant.getSceneId();
        }

        LocalDateTime beginTime = query.getBeginLocalDateTime();
        LocalDateTime endTime = query.getEndLocalDateTime();
        LambdaQueryWrapper<SysTenant> queryWrapper = SysTenant.lqw()
                .like(StringUtils.isNotEmpty(query.getName()), SysTenant::getName, query.getName())
                .eq(StringUtils.isNotEmpty(query.getStatus()), SysTenant::getStatus, query.getStatus())
                .ge(Objects.nonNull(beginTime), SysTenant::getCreatedAt, beginTime)
                // 如果不是管理场景的租户则展示当前租户下的所有子租户
                .eq(!tenant.getIsMgtScene(), SysTenant::getMainTenantId, tenant.getMainTenantId())
                .eq(Objects.nonNull(sceneId), SysTenant::getSceneId, sceneId)
//                .eq(tenant.getIsSysAdmin(), SysTenant::getIsMainTenant, true) // 暂时设置如果为系统租户, 则只展示主租户
                .le(Objects.nonNull(endTime), SysTenant::getCreatedAt, endTime)
                .orderByDesc(BasePlusPO::getCreatedAt);

        IPage<SysTenant> page = this.page(PageUtil.to(query), queryWrapper);
        return PageUtil.to(page, (e) -> {
            TenantResp resp = tenantAssembly.toTenantResp(e);
            resp.setUrl(tenantProperties.getUrl(resp.getId()));
            return resp;
        });
    }

    @Override
    public List<SysTenant> listAll() {
        return baseMapper.selectList(SysTenant.lqw().eq(SysTenant::getStatus, BoolEnum.TRUE.getCode()));
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public String saveTenant(TenantSaveReq req) {
        // 默认设置成false
        req.setIsMgtSceneTenant(ObjectUtils.firstNonNull(req.getIsMgtSceneTenant(), Boolean.FALSE));

        LoginUser loginUser = SecurityUtils.getLoginUser();
        // 是否将当前用户设置为管理员用户
        boolean currentUserAsAdmin = loginUser.getUserId().equals(req.getAdminUserId());
        String id = String.valueOf(YitIdHelper.nextId());
        // 默认就是主租户
        String mainTenantId = id;

        // 如果不是平台租户, 则主租户id就采用登录用户所拥有的主租户id
        AuthUserTenant loginTenant = SecurityUtils.getLoginUser().getTenant();
        if (!loginTenant.getIsMgtScene()) {
            // 如果在非主租户下创建, 则为子租户
            mainTenantId = loginTenant.getMainTenantId();

            // 非主租户拿主租户的套餐id
            SysTenant mainTenant = tenantManager.getById(mainTenantId);
            Assert.notNull(mainTenant, new BizException("主租户不存在"));
            req.setPackageId(mainTenant.getSubPackageId());
            req.setAdminUserId(mainTenant.getAdminUserId());
            req.setSceneId(mainTenant.getSceneId());
        } else if (!req.getIsMgtSceneTenant() && loginTenant.getIsMgtScene()){
            Assert.notNull(req.getPackageId(), new BizException("请选择套餐"));
            Assert.notNull(req.getAdminUserId(), new BizException("请选择管理员"));
            Assert.notNull(req.getSceneId(), new BizException("请选择场景"));

            // 如果子租户套餐为空, 则默认同主租户套餐
            req.setSubPackageId(ObjectUtil.defaultIfNull(req.getSubPackageId(), req.getPackageId()));
        }

        // 校验套餐被禁用
        SysTenantPackage tenantPackage = tenantPackageManager.validTenantPackage(req.getPackageId());

        // 校验域名
        tenantManager.validSaveTenant(req);

        // 创建租户
        SysTenant tenant = tenantAssembly.toTenant(req);
        tenant.setId(id);
        tenant.setIsMainTenant(AuthUserTenant.mainTenant(mainTenantId, id));
        tenant.setMainTenantId(mainTenantId);
        this.save(tenant);

        TenantUtils.execute(tenant.getId(), () -> {
            // 创建角色, 并分配权限
            Long roleId = saveRole(tenantPackage);
            // 检查并为用户分配角色
            Long userId = processUser(roleId, req);
            this.updateById(new SysTenant().setId(tenant.getId()).setAdminUserId(userId));
            // 保存租户用户关系
            SysTenantUser tenantUser = new SysTenantUser()
                    .setTenantId(tenant.getId()).setUserId(userId)
                    .setSceneId(tenant.getSceneId());
            tenantUserManager.save(tenantUser);

            // 创建租户网站配置
            saveSiteConfig(tenant);
        });

        // 获取租户用户权限, 调用切换租户方法, 触发授权信息变更
//        if (currentUserAsAdmin) {
//            SwitchTenantReq switchReq = new SwitchTenantReq();
//            switchReq.setTenantId(tenant.getId());
//            tenantUserService.switchTenant(switchReq);
//        }
        return tenant.getId();
    }

    private void saveSiteConfig(SysTenant tenant) {
        WebSiteParam param = new WebSiteParam().setLogo(tenant.getLogo())
                .setName(tenant.getName()).setSiteDescTitle(tenant.getLogo());
        ConfigSaveReq req = new ConfigSaveReq();
        req.setConfigKey(ConfigKeyEnum.WEB_SITE.getKey());
        req.setConfigName(ConfigKeyEnum.WEB_SITE.getKey());
        req.setConfigValue(JSON.toJSONString(param));
        configService.saveConfig(req);
    }

    private Long processUser(Long roleId, TenantSaveReq req) {
        if (Objects.isNull(req.getAdminUserId())) {
            throw new BizException("请选择管理员");
        }
        UserResp userResp = userService.getUserById(req.getAdminUserId());
        if (Objects.isNull(userResp)) {
            throw new BizException("用户不存在");
        }
        authorizeService.assignUserRole(req.getAdminUserId(), Collections.singletonList(roleId));
        return userResp.getId();
    }

    private Long saveRole(SysTenantPackage tenantPackage) {
        // 创建角色
        RoleSaveReq req = new RoleSaveReq();
        req.setRoleName(RoleEnum.OWNER.getName());
        req.setRoleCode(RoleEnum.OWNER.getCode());
        req.setRoleSort(Integer.valueOf(RoleEnum.OWNER.getSort())).setRemark("系统自动生成");

        Long roleId = roleService.saveRole(req);

        // 分配权限
        if (Objects.nonNull(tenantPackage)) {
            authorizeService.assignRoleMenu(roleId, tenantPackage.getMenuIds());
        }
        return roleId;
    }

    private SysTenant validateUpdateTenant(String id) {
        SysTenant tenant = this.getById(id);
        if (tenant == null) {
            throw new BizException(TENANT_NOT_EXISTS);
        }

        // 内置租户，不允许操作
        if (TenantCons.SYSTEM_TENANT_ID.compareTo(id) == 0) {
            throw new BizException(TENANT_CAN_NOT_UPDATE_SYSTEM);
        }
        return tenant;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateTenant(TenantUpdateReq req) {
        // 校验存在
        SysTenant tenant = validateUpdateTenant(req.getId());
        // 校验域名
        tenantManager.validUpdateTenant(req);

        SysTenant updateObj = tenantAssembly.toTenant(req);

        // 校验套餐被禁用
        SysTenantPackage tenantPackage = tenantPackageManager.validTenantPackage(req.getPackageId());

        // 更新操作
        this.updateById(updateObj);

        // 如果是主租户, 则更新所有子租户套餐id
        if (tenant.getIsMainTenant() && Objects.nonNull(req.getSubPackageId())) {
            tenantManager.updateSubTenantPackageId(tenant.getId(), req.getSubPackageId());
            // 校验子租户套餐
            SysTenantPackage subTenantPackage = tenantPackageManager.validTenantPackage(req.getSubPackageId());
            if (Objects.nonNull(subTenantPackage)) {
                // 获取所有子租户
                List<SysTenant> subTenantList = tenantManager.listSubTenant(tenant.getId());
                // 无论套餐是是否变化，都修改其角色的权限
                subTenantList.forEach(e -> updateTenantRoleMenu(e.getId(), subTenantPackage.getMenuIds()));
            }
        }

        // 无论套餐是是否变化，都修改其角色的权限
        if (Objects.nonNull(tenantPackage)) {
            updateTenantRoleMenu(tenant.getId(), tenantPackage.getMenuIds());
        }

        // 删除缓存
        String cacheKey = CacheCons.TENANT_KEY_DOMAIN + tenant.getDomain();
        cacheService.opsForKey().delete(cacheKey);

        // 删除租户对应的管理场景的租户id缓存
        redisTemplate.delete(CacheCons.SCENE_MGT_TENANT);
        return true;
    }

    @Override
    public void updateTenantRoleMenu(String tenantId, Set<Long> menuIds) {
        TenantUtils.execute(tenantId, () -> {
            // 获得所有角色
            List<SysRole> roles = roleManager.list();
            // 校验是否存在数据越权问题
            roles.forEach(role -> Assert.isTrue(tenantId.equals(role.getTenantId()), "角色({}/{}) 租户不匹配",
                    role.getId(), role.getTenantId(), tenantId));
            // 重新分配每个角色的权限
            roles.forEach(role -> {
                // 如果是租户拥有者，重新分配其权限为租户套餐的权限
                if (Objects.equals(role.getRoleCode(), RoleEnum.OWNER.getCode())) {
                    authorizeService.assignRoleMenu(role.getId(), menuIds);
                    log.info("[updateTenantRoleMenu][租户拥有者({}/{}) 的权限修改为({})]", role.getId(), role.getTenantId(), menuIds);
                    return;
                }
                // 如果是其他角色，则去掉超过套餐的权限
                Set<Long> roleMenuIds = authorizeService.listRoleMenuId(role.getId());
                roleMenuIds = CollUtil.intersectionDistinct(roleMenuIds, menuIds);
                authorizeService.assignRoleMenu(role.getId(), roleMenuIds);
                log.info("[updateTenantRoleMenu][角色({}/{}) 的权限修改为({})]", role.getId(), role.getTenantId(), roleMenuIds);
            });
        });
    }

    @Override
    public TenantResp getTenantById(String id) {
        SysTenant tenant = this.getById(id);
        if (Objects.isNull(tenant)) {
            throw new BizException(TENANT_NOT_EXISTS);
        }
        TenantResp resp = tenantAssembly.toTenantResp(tenant);

        SysTenantPackage tenantPackage = tenantPackageManager.getById(tenant.getPackageId());
        TenantUtils.execute(id, () -> {
            LocalUser localUser = authApi.getUserByUserId(tenant.getAdminUserId());
            resp.setUserName(Optional.ofNullable(localUser).map(LocalUser::getUserName).orElse(""));
            resp.setUrl(tenantProperties.getUrl(resp.getId()));
            resp.setPackageName(Optional.ofNullable(tenantPackage).orElse(new SysTenantPackage()).getName());
        });
        return resp;
    }

    @Override
    public SysTenant getByDomain(String domain) {
        String cacheKey = CacheCons.TENANT_KEY_DOMAIN + domain;
        Object obj = cacheService.opsForValue().get(cacheKey);
        if (Objects.isNull(obj)) {
            LambdaQueryWrapper<SysTenant> lqw = SysTenant.lqw().eq(SysTenant::getDomain, domain);
            obj = this.getOne(lqw);
            cacheService.opsForValue().set(cacheKey, obj);
        }
        return (SysTenant) obj;
    }

    @Override
    public TenantConfigResp getConfig() {
        return tenantAssembly.toTenantConfigResp(tenantProperties);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateOwnerTenant(TenantOwnerUpdateReq req) {
        SysTenant tenant = tenantAssembly.toTenant(req);
        tenant.setId(TenantContext.get().getTenantId());

        SysTenant dbTenant = this.getById(TenantContext.get().getTenantId());
        if (Objects.isNull(dbTenant)) {
            throw new BizException("租户不存在");
        }
        this.updateById(tenant);

        // 删除缓存
        String cacheKey = CacheCons.TENANT_KEY_DOMAIN + dbTenant.getDomain();
        cacheService.opsForKey().delete(cacheKey);
    }

    @Override
    public SysTenant getUserCurrentTenant(Long userId) {
        List<SysTenantUser> list = tenantUserManager.list(SysTenantUser.lqw().eq(SysTenantUser::getUserId, userId));
        SysTenantUser tenantUser = list.stream().findFirst().orElse(null);
        if (Objects.isNull(tenantUser)) {
            return null;
        }
        return tenantManager.getById(tenantUser.getTenantId());
    }

    @Override
    public String getMgtSceneTenantId(String tenantId) {
        String cacheKey = CacheCons.SCENE_MGT_TENANT;
        String mgtTenantId;
        Object cacheValue = redisTemplate.opsForHash().get(cacheKey, tenantId);
        if (Objects.isNull(cacheValue)) {
            SysTenant tenant = this.getById(tenantId);
            if (Objects.isNull(tenant)) {
                throw new BizException("租户 [{}] 不存在", tenantId);
            }
            LambdaQueryWrapper<SysTenant> lqw = SysTenant.lqw().eq(SysTenant::getIsMgtSceneTenant, true)
                    .eq(SysTenant::getSceneId, tenant.getSceneId());
            SysTenant mgtSceneTenant = this.getOne(lqw);
            if (Objects.isNull(mgtSceneTenant)) {
                throw new BizException("尚未设置管理场景 [{}] 的租户", tenant.getSceneId());
            }
            redisTemplate.opsForHash().put(cacheKey, tenantId , mgtSceneTenant.getId());
            mgtTenantId = mgtSceneTenant.getId();
        } else {
            mgtTenantId = (String) cacheValue;
        }
        return mgtTenantId;
    }

    @Override
    public String getMainTenantId(String tenantId) {
        String cacheKey = CacheCons.MAIN_TENANT;
        String mainTenantId;
        Object cacheValue = redisTemplate.opsForHash().get(cacheKey, tenantId);
        if (Objects.isNull(cacheValue)) {
            SysTenant tenant = this.getById(tenantId);
            if (Objects.isNull(tenant)) {
                throw new BizException("租户 [{}] 不存在", tenantId);
            }
            redisTemplate.opsForHash().put(cacheKey, tenantId , tenant.getMainTenantId());
            mainTenantId = tenant.getMainTenantId();
        } else {
            mainTenantId = (String) cacheValue;
        }
        return mainTenantId;
    }

    @Override
    public List<TenantResp> listSubTenant(String tenantId) {
        LambdaQueryWrapper<SysTenant> lqw = SysTenant.lqw().eq(SysTenant::getMainTenantId, tenantId);
        List<SysTenant> list = this.list(lqw);
        return list.stream().map(tenantAssembly::toTenantResp).collect(Collectors.toList());
    }

    @Override
    public List<TenantResp> listTenantByIds(List<String> tenantIds) {
        if (CollUtil.isEmpty(tenantIds)) {
            return Collections.emptyList();
        }
        return this.listByIds(tenantIds).stream().map(tenantAssembly::toTenantResp).collect(Collectors.toList());
    }
}
